package myClient.session;

import component.userInformation.UserAttribute;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.AttributeKey;
import lombok.Data;
import protobuf.protoClass.ProtoMsg;

import java.util.UUID;

/**
 * <pre>
 *      为了让通道 (Channel)连接和用户(User)状态的管理与使用变得方便,使用了一个非常重要的概念——会话（Session）
 *      Session和 Channel之间存在两个方向的导航关系：
 *          正向导航: 可以通过 Session导航到 Channel，
 *                  主要用于出站处理的场景，例如需要将数据包写出到通道
 *          反向导航: 可以通过 Channel导航到 Session，
 *                  主要用于入站处理的场景，入站时可以从通道获取绑定的会话，以便进一步进行业务处理。
 *
 *      主要完成：
 *              1. Session与 Channel的互相绑定
 *              2. 通过 ProtoMsg.Message 设置 Channel绑定的 Session
 *
 *      ClientSession绑定在 Channel上，
 *      因而可以在入站处理时通过 Channel反向取得绑定的 ClientSession，
 *      从而对应到 user信息。
 *
 * </pre>
 */

@Data
public class ClientSession {

    /**
     * <pre>
     *     +---------------------------------------------------+
     *     |                     AttributeMap                  |
     *     |   +--------------+                +-----------+   |
     *     |   | AttributeKey |                | Attribute |   |
     *     |   |   +-----+    |  (key,value)   | +-------+ |   |
     *     |   |   | Key |    | -------------> | | value | |   |
     *     |   |   +-----+    |                | +-------+ |   |
     *     |   +--------------+                +-----------+   |
     *     +---------------------------------------------------+
     *
     *     AttributeMap 绑定 Channel ,AttributeMap可以被共享
     *     AttributeMap 绑定 ChannelHandlerContext,AttributeMap不共享
     *                       各自的 AttributeMap 互不相干，各不影响
     *
     *   1.
     *     创建 AttributeKey , AttributeMap中储存 ClientSession类型的 Attribute值。
     *     AttributeKey< ClientSession > SESSION_KEY = AttributeKey.valueOf("SESSION_KEY");
     *   2.
     *     通过 attr()方法,来获取 AttributeKey对应的 Attribute。
     *     通过 Attribute的 set()方法来设置 value。
     *     通过 Attribute的 get()方法来获取 value。
     *     channel.attr(SESSION_KEY).set(this);
     *     channel.attr(SESSION_KEY).get();
     *
     *   AttributeKey 包装 Key
     *   Attribute    包装 value
     * </pre>
     */

    public static final AttributeKey<ClientSession> SESSION_KEY = AttributeKey.valueOf("SESSION_KEY");

    /**
     * 通过channel，ClientSession可以向服务端发送消息。
     */
    private Channel channel;
    /**
     * 通过 userAttribute，ClientSession可以获得当前的用户信息。
     */
    private UserAttribute userAttribute;
    /**
     * 通用字段：会话id
     */
    private String sessionID;
    /**
     * 客户端会话ClientSession保存着当前的状态: 是否成功连接
     */
    private boolean isConnected = false;
    /**
     * 客户端会话ClientSession保存着当前的状态: 是否成功登录
     */
    private boolean isLogin = false;

    /**
     * 构造器，完成 Session与 Channel的互相绑定
     */
    public ClientSession(Channel channel) {
        //正向绑定,将 Channel绑定到 Session
        this.channel = channel;
        //设置唯一 session ID
        this.sessionID = UUID.randomUUID().toString();
        //反向绑定,将 Session绑定到 Channel
        channel.attr(SESSION_KEY).set(this);
    }

    /**
     * 用户登陆成功，设置 Session。
     * 通过 ProtoMsg.Message设置 Channel(通过 ChannelHandlerContext获取)绑定的 Session
     */
    public static void loginSuccess(ChannelHandlerContext context, ProtoMsg.Message message) {
        ClientSession session = getSessionByContext(context);
        session.setSessionID(message.getSessionId());
        session.setLogin(true);

        System.out.println("登陆成功");
    }

    /**
     * 获取 Channel(通过 ChannelHandlerContext获取)绑定的 Session
     */
    public static ClientSession getSessionByContext(ChannelHandlerContext context) {
        return context.channel()
                .attr(SESSION_KEY)
                .get();
    }

    /**
     * 通过 Session绑定的 Channel ，获取 RemoteAddress
     */
    public String getRemoteAddress() {
        return channel.remoteAddress().toString();
    }

    /**
     * 向 Channel写入 message并 flush
     */
    public ChannelFuture writeAndFlush(Object message) {
        return channel.writeAndFlush(message);
    }

    /**
     * 向 Channel写入 message并 close
     */
    public void writeAndClose(Object message) {
        ChannelFuture future = channel.writeAndFlush(message);
        future.addListener(ChannelFutureListener.CLOSE);
    }

    /**
     * 通过 Session关闭连接
     */
    public void closeChannelBySession() {
        isConnected = false;
        //关闭连接
        ChannelFuture future = channel.close();
        future.addListener((ChannelFutureListener) tmpFuture -> {
            if (tmpFuture.isSuccess()) {
                System.out.println("连接已关闭。。。");
            }
        });
    }

}
